package com.codejoys.monitor.service.impl;

import com.codejoys.monitor.Constants;
import com.codejoys.monitor.dao.DeviceDao;
import com.codejoys.monitor.domain.*;
import com.codejoys.monitor.dto.ChannelConfigMap;
import com.codejoys.monitor.dto.DeviceDto;
import com.codejoys.monitor.exception.SysException;
import com.codejoys.monitor.jna.CANLib;
import com.codejoys.monitor.mapper.ChannelMapper;
import com.codejoys.monitor.mapper.ConfigMapper;
import com.codejoys.monitor.mapper.DeviceMapper;
import com.codejoys.monitor.mapper.SysConfigMapper;
import com.codejoys.monitor.service.ModbusService;
import com.codejoys.monitor.service.StatusService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class StatusServiceImpl implements StatusService {
    private static final Logger logger = LoggerFactory.getLogger(StatusServiceImpl.class);

    @Autowired
    ModbusService modbusService;

    @Autowired
    DeviceMapper deviceMapper;

    @Autowired
    DeviceDao deviceDao;

    @Autowired
    ConfigMapper configMapper;

    @Autowired
    ChannelMapper channelMapper;

    @Autowired
    SysConfigMapper sysConfigMapper;

    // 发送Modbus消息，打开电源
    // 打开CAN控制器，设置电压、电流
    // 调用CAN协议，打开模块
    @Override
    public int start(DeviceDto deviceDto) {
        logger.info("开始启动设备：{}",
                deviceDto.getId(), deviceDto.getChannel(), deviceDto.getSn());

        int result = modbusService.startPower(deviceDto);

        return result;
    }

    @Override
    public int close(DeviceDto deviceDto) {
        logger.info("开始停止设备：{}",
                deviceDto.getId(), deviceDto.getChannel(), deviceDto.getSn());

        int result = modbusService.stopPower(deviceDto);
        return 0;
    }

    @Override
    public void remove(DeviceDto deviceDto) {
        logger.info("移除设备：{}",
                deviceDto.getId(), deviceDto.getChannel(), deviceDto.getSn());

        if (deviceDto.getStatus() == Constants.Status.AGING.value()) {
            throw new SysException(1200, "设备正在老化中，停止老化后才能移除！");
        }

        Device d = new Device();
        d.setId(deviceDto.getId());
        d.setIsRemoved((byte) 1);
        d.setOfflineTime(new Date());

        // 更新设备状态
        deviceMapper.updateByPrimaryKeySelective(d);
    }

    @Override
    public void startAll(List<DeviceDto> devices) {
        modbusService.startAll(devices);
    }

    @Override
    public void stopAll(List<DeviceDto> devices) {
        modbusService.stopAll(devices);
    }

    @Override
    public int getStatus(DeviceDto deviceDto) {
        return 0;
    }

    @Override
    public void sendAlert(DeviceDto deviceDto) {
        modbusService.sendAlarm(deviceDto);
    }

    @Override
    public void batchDetach(List<DeviceDto> devices) {
        logger.info("批量移除设备：{}",
                devices.stream().map(d -> d.getSn()).collect(Collectors.toList()));

        if (devices == null || devices.size() == 0) return;

        List<Integer> ids = devices.stream().map(d -> d.getId()).collect(Collectors.toList());

        deviceDao.batchDetach(ids, new Date());
    }

    @Autowired
    CANLib canLib;

    @Autowired
    ChannelConfigMap configMap;

    @Override
    public List<DeviceDto> findDevicesWithErrorData() {
        // 把所有正在老化的设备查询出来
        final List<DeviceDto> list = deviceDao.selectAgingDevices();

        logger.info("正在老化的设备：{}", list.stream().map(d -> d.getSn()).collect(Collectors.toList()));

        // 如果开机时间小于10分钟，不进行数据校验  /要求这个时间可以自行设置
        SysConfigExample example =  new SysConfigExample();
        example.createCriteria().andNameEqualTo(Constants.CONFIG_KEY_ERROR_CHECK_TIME);
        // 查询数据库中的错误检测时间
        List<SysConfig> configs = sysConfigMapper.selectByExample(example);

        final int minutes = (configs != null && configs.size() > 0)
                ? Integer.valueOf(configs.get(0).getValue())
                : Constants.CONFIG_VALUE_ERROR_CHECK_TIME;          // 默认5分钟

        // 如果开机时间小于10分钟，不进行数据校验  /要求这个时间可以自行设置
        final List<DeviceDto> devices = list.stream().filter(d -> {
            final long now = new Date().getTime();
            final long start = d.getAgingStartTime().getTime();

            // 开机10分钟，不进行数据判断
            if (now - start < minutes * 60 * 1000) {
                return false;
            }

            return true;
        }).collect(Collectors.toList());

        if (devices == null || devices.size() == 0) {
            return null;
        }

        logger.info("老化超过{}分钟的设备：{}",
                minutes,
                devices.stream().map(d -> d.getSn()).collect(Collectors.toList()));

        // 查询设备最新的10条数据
        // ids：所有正在老化的设备id
        final List<Integer> ids = devices.stream().map(d -> d.getId()).collect(Collectors.toList());

        // 查询所有正在老化的设备的温度、电流电压数据（每个设备查10条，相当于5分钟之内的数据）
        List<DeviceData> data = deviceDao.findLastDataItems(ids, 10);

        // 上面是所有正在老化的设备数据，下面这个操作，将数据分组，使用一个map，将每个设备的数据，映射到设备的id
        // 设备id => 设备数据
        final Map<Integer, List<DeviceData>> dataLookup = data.stream()
                .collect(Collectors.groupingBy(DeviceData::getDeviceId));

        // 遍历每个设备，看数据是否有错
        final List<DeviceDto> dtos = devices.stream().filter(d -> {
            // 找到设备对应的数据
            final List<DeviceData> deviceData = dataLookup.get(d.getId());

            if (deviceData == null) {
                logger.warn("没有设备的数据记录：{}", d.getSn());

                // 没有设备的数据记录，可能设备断电了，或者从来未开机过，报错
                return true;
            }

            // 判断数据是否有错
            return this.isDeviceDataError(d, deviceData);
        }).collect(Collectors.toList());

        return dtos;
    }

    /**
     * 获取通道的设备状态
     *
     */
    @Override
    public List<DeviceDto> getChannelStatus(String[] channels) {
        // 查询通道信息
        final List<DeviceDto> dtos = deviceDao.channelsStatusList(channels);

        if (dtos == null || dtos.size() == 0) {
            return new ArrayList<>();
        }

        // 查询设备的最新温度、电流电压（分两次查询，性能会好一些）
        final List<Integer> ids = dtos.stream()
                .filter(dto -> dto.getId() != null)     // 只查询关联了通道的设备
                .filter(dto -> dto.getStatus() == Constants.Status.AGING.value())   // 只查询运行的设备
                .map(dto -> dto.getId())
                .collect(Collectors.toList());

        // 没有老化中的设备
        if (ids == null || ids.size() == 0) return dtos;

        final List<DeviceData> deviceData = deviceDao.statusDataList(ids);
        final Map<Integer, DeviceData> dataLookup = deviceData
                .stream()
                .collect(Collectors.toMap(DeviceData::getDeviceId, c -> c));

        dtos.forEach(dto -> {
            final DeviceData data = dataLookup.get(dto.getId());
            if (data != null) {
                dto.setVoltage(data.getVoltage());
                dto.setCurrent(data.getCurrent());
                dto.setTemperature(data.getTemperature());
            }
        });

        return dtos;
    }

    // 设备重置
    @Override
    public void restore(DeviceDto device) {
        if (!Constants.Status.isError(device.getStatus())) {
            logger.error("只能重置错误状态的设备！device: {}，status: {}", device.getSn(), device.getStatus());
            return;
        }

        // 设备解绑
        remove(device);

        // 模拟设备重新扫码，添加到通道
        Device d = new Device();

        d.setSn(device.getSn());
        d.setChannelId(device.getChannelId());
        d.setModel(device.getModel());
        d.setAgingTimes(device.getAgingTimes());
        d.setPresetVoltage(device.getPresetVoltage());
        d.setPresetCurrent(device.getPresetCurrent());
        d.setPresetDuration(device.getPresetDuration());
        d.setVoltageUpper(device.getVoltageUpper());
        d.setVoltageLower(device.getVoltageLower());
        d.setCurrentUpper(device.getCurrentUpper());
        d.setCurrentLower(device.getCurrentLower());
        d.setStatus((byte) Constants.Status.READY.value());     // 已扫码
        d.setIsRemoved((byte) 0);   // 0：绑定到设备  1：已移除
        d.setCreated(new Date());
        d.setCreatedBy(device.getCreatedBy());
        d.setDeleted((byte) 0);

        deviceMapper.insert(d);

        logger.info("设备重置成功！");
    }

    // 启动重置
    @Override
    public void restoreOnStart(DeviceDto device) {
        logger.info("重置设备状态。");
        restore(device);

        logger.info("关闭设备电源！");
        modbusService.stopPowerOnStart(device);
    }

    // 判断方式：最近的10条数据，每条的电流、电压都要在阈值范围之内
    // 目前为了快速过滤数据，只判断电压大于30（因为目前异常数据基本都是电压为一个极小值）
    private boolean isDeviceDataError(DeviceDto d, List<DeviceData> deviceData) {
        // 如果设备的数据都是5分钟之前的，说明设备通信已经有问题了，报错
        long now = new Date().getTime();
        final boolean isOldData = deviceData.stream().allMatch(data -> {
            return (now - data.getCreated().getTime()) >= 5 * 60 * 1000;
        });

        if (isOldData) {
            logger.warn("设备最近5分钟没有数据！{} {}", d.getChannel(), d.getSn());
            return true;
        }

        // 10条数据里面，只要有一个是正常的，就不报错
        //根据设备的型号去查询电压电流上下限

        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss" );
        Date date = new Date();
        String time = format.format(date);
        // 判断电压是否正常
        if (d.getVoltageLower() != null && d.getVoltageUpper() != null) {
            final double vLower = d.getVoltageLower().doubleValue();
            final double vUpper = d.getVoltageUpper().doubleValue();

            final StringBuilder sb = new StringBuilder("");
            // 只要有一个时正常的即可
            final boolean isVoltageNormal = deviceData.stream().anyMatch(data -> {
                boolean isVoltageHigh = data.getVoltage().doubleValue() > vUpper;
                boolean isVoltageLow = data.getVoltage().doubleValue() < vLower;
                if (isVoltageHigh) {
                    logger.warn("电压过高！实际值：{} 上限：{}", data.getVoltage(), vUpper);

                    if (sb.length() == 0) {
                        sb.append("电压超出设定上限   "+ time);
                    }
                }

                if (isVoltageLow) {
                    logger.warn("电压过低！实际值：{}， 下限：{}", data.getVoltage(), vLower);
                    if (sb.length() == 0) {
                        sb.append("电压低于设定下限   "+ time);
                    }
                }
                
                return (!isVoltageHigh && !isVoltageLow);
            });

            if (!isVoltageNormal) {
                if (d.getAgingErrorRemark() == null) {
                    d.setAgingErrorRemark(sb.toString());
                } else {
                    d.setAgingErrorRemark(d.getAgingErrorRemark() + "\n" + sb.toString());
                }
                return true;
            }
        } else {
            logger.warn("未设定电压阈值！");
        }

        // 判断电流是否正常
        if (d.getCurrentUpper() != null && d.getCurrentLower() != null) {
            final double cLower = d.getCurrentLower().doubleValue();
            final double cUpper = d.getCurrentUpper().doubleValue();

            final StringBuilder sb = new StringBuilder("");

            // 只要有一个正常的即可
            final boolean isCurrentNormal = deviceData.stream().anyMatch(data -> {
                // 目前电流设定的是最大值，不是标准电流，这里只判断电流持续上不去的情况
                boolean isCurrentHigh = data.getCurrent().doubleValue() > cUpper;
                boolean isCurrentLow = data.getCurrent().doubleValue() < cLower;
                if (isCurrentHigh) {
                    logger.warn("电流过高！实际值：{} 上限：{}", data.getCurrent(), cUpper);
                    if (sb.length() == 0) { // 避免多次添加同样的备注
                        sb.append("电流超出设定上限   " + time);
                    }
                }

                if (isCurrentLow) {
                    logger.warn("电流过低！实际值：{} 下限：{}", data.getCurrent(), cLower);
                    if (sb.length() == 0) { // 避免多次添加同样的备注
                        sb.append("电流低于设定下限  " + time);
                    }
                }

                return (!isCurrentLow && !isCurrentHigh);
            });

            if (!isCurrentNormal) {
                if (d.getAgingErrorRemark() == null) {
                    d.setAgingErrorRemark(sb.toString());
                } else {
                    d.setAgingErrorRemark(d.getAgingErrorRemark() + "\n" + sb.toString());
                }
                return true;    // 电流不正常
            }
        } else {
            logger.warn("未设定电流阈值！");
        }

        return false;   // 正常
    }
}
